﻿namespace Microsoft.VisualStudio.TextTemplating.CommandLine
{
    using Microsoft.VisualStudio.TextTemplating;
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Policy;
    using System.Text;

    [Command("TextTransform", BriefUsage="TextTransform [<options>] <templateName>", BriefDescription="Transforms the given text template.", GeneralInformation="This command runs a transform using a text template.", AllowNoNameOptions=true)]
    internal class CommandLineHost : Command, ITextTemplatingEngineHost
    {
        private StringCollection directiveProcessors = new StringCollection();
        private bool encodingSetFromOutputDirective;
        private ITextTemplatingEngine engine;
        private string extension = ".txt";
        private StringCollection imports = new StringCollection();
        private StringCollection includeDirectories = new StringCollection();
        private string inputFile;
        private static bool inResolve;
        private Encoding outputEncoding;
        private string outputFile;
        private StringCollection parameters = new StringCollection();
        private StringCollection referencePaths = new StringCollection();
        private StringCollection references = new StringCollection();

        private static log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
        private Assembly AssemblyResolver(object sender, ResolveEventArgs args)
        {
            if (!inResolve)
            {
                string y = this.ResolveAssemblyReference(args.Name);
                if (StringComparer.OrdinalIgnoreCase.Compare(args.Name, y) != 0)
                {
                    Assembly assembly = Assembly.LoadFrom(y);
                    if (assembly == null)
                    {
                        base.Error.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.AssemblyNotFound, new object[] { args.Name }));
                        throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.AssemblyNotFound, new object[] { args.Name }));
                    }
                    return assembly;
                }
                foreach (string str2 in this.ReferencePaths)
                {
                    AppDomain domain = null;
                    try
                    {
                        AppDomainSetup info = new AppDomainSetup {
                            ApplicationBase = str2,
                            ApplicationName = "temp",
                            PrivateBinPath = str2
                        };
                        domain = AppDomain.CreateDomain("temp", null, info);
                        if (domain != null)
                        {
                            AssemblyQuery query = domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, "Microsoft.VisualStudio.TextTemplating.CommandLine.AssemblyQuery") as AssemblyQuery;
                            if (query != null)
                            {
                                string assemblyLocation = null;
                                try
                                {
                                    inResolve = true;
                                    assemblyLocation = query.GetAssemblyLocation(args.Name);
                                }
                                finally
                                {
                                    inResolve = false;
                                }
                                if (!string.IsNullOrEmpty(assemblyLocation))
                                {
                                    return Assembly.LoadFrom(assemblyLocation);
                                }
                            }
                        }
                        continue;
                    }
                    finally
                    {
                        if (domain != null)
                        {
                            AppDomain.Unload(domain);
                        }
                    }
                }
            }
            return null;
        }

        public object GetHostOption(string optionName)
        {
            return null;
        }

        protected override void LoadImplementation(CommandOptionCollection options)
        {
            if (!options.Contains("") || (options[""].Values.Count != 1))
            {
                throw new UsageException(Resources.MissingTemplateFileName);
            }
            this.inputFile = options[""].Values[0];
            if (!File.Exists(this.inputFile))
            {
                throw new FileNotFoundException(Resources.TemplateNotFound, this.inputFile);
            }
        }

        public bool LoadIncludeText(string requestFileName, out string content, out string location)
        {
            if (Path.IsPathRooted(requestFileName))
            {
                Zone zone = Zone.CreateFromUrl(new Uri(requestFileName).AbsoluteUri);
                if ((zone.SecurityZone == SecurityZone.Trusted) || (zone.SecurityZone == SecurityZone.MyComputer))
                {
                    content = ReadFileContent(requestFileName);
                    location = requestFileName;
                    return true;
                }
                this.LogError(false, string.Format(CultureInfo.CurrentCulture, Resources.IncludeNotFound, new object[] { string.Format(CultureInfo.CurrentCulture, Resources.PathNotTrusted, new object[] { requestFileName }) }));
                content = string.Empty;
                location = string.Empty;
                return false;
            }
            List<string> list = new List<string> {
                Path.GetDirectoryName(this.inputFile)
            };
            foreach (string str in this.IncludeDirectories)
            {
                list.Add(str);
            }
            foreach (string str2 in list)
            {
                string path = Path.Combine(str2, requestFileName);
                if (File.Exists(path))
                {
                    content = ReadFileContent(path);
                    location = path;
                    return true;
                }
            }
            content = string.Empty;
            location = string.Empty;
            return false;
        }

        private void LogError(bool isWarning, string message)
        {
            if (isWarning)
            {
                m_log.Warn(message);
                Console.Error.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.WarningMessage, new object[] { message }));
            }
            else
            {
                base.ReturnCode = 1;
                m_log.Error(message);
                Console.Error.WriteLine(string.Format(CultureInfo.CurrentCulture, Resources.ErrorMessage, new object[] { message }));
            }
        }

        public void LogErrors(CompilerErrorCollection errors)
        {
            if (errors == null)
            {
                throw new ArgumentNullException("errors");
            }
            foreach (CompilerError error in errors)
            {
                m_log.Warn(error.ToString());
                Console.Error.WriteLine(error.ToString());
                if (!error.IsWarning)
                {
                    base.ReturnCode = 1;
                }
            }
        }

        public AppDomain ProvideTemplatingAppDomain(string content)
        {
            return AppDomain.CurrentDomain;
        }

        private static string ReadFileContent(string fileName)
        {
            using (StreamReader reader = new StreamReader(fileName))
            {
                return reader.ReadToEnd();
            }
        }

        public string ResolveAssemblyReference(string assemblyReference)
        {
            string location;
            if (Path.IsPathRooted(assemblyReference))
            {
                Zone zone = Zone.CreateFromUrl(new Uri(assemblyReference).AbsoluteUri);
                if ((zone.SecurityZone == SecurityZone.Trusted) || (zone.SecurityZone == SecurityZone.MyComputer))
                {
                    return assemblyReference;
                }
                this.LogError(false, string.Format(CultureInfo.CurrentCulture, Resources.AssemblyNotFound, new object[] { string.Format(CultureInfo.CurrentCulture, Resources.PathNotTrusted, new object[] { assemblyReference }) }));
                return string.Empty;
            }
            //using (StringEnumerator enumerator = this.ReferencePaths.GetEnumerator())
            //{
            //    while (enumerator.MoveNext())
            //    {
            //        location = Path.Combine(enumerator.Current, assemblyReference);
            //        if (File.Exists(location))
            //        {
            //            return location;
            //        }
            //    }
            //}
            foreach(string  enumerator in this.ReferencePaths)
            {                
                location = Path.Combine(enumerator, assemblyReference);
                if (File.Exists(location))
                {
                    return location;
                }                
            }
            location = GlobalAssemblyCacheHelper.GetLocation(assemblyReference);
            if (!string.IsNullOrEmpty(location))
            {
                return location;
            }
            return assemblyReference;
        }

        public Type ResolveDirectiveProcessor(string processorName)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeof(DirectiveProcessorRecord));
            try
            {
                foreach (string str in this.DirectiveProcessors)
                {
                    DirectiveProcessorRecord record = converter.ConvertFromString(str) as DirectiveProcessorRecord;
                    if (((record != null) && (StringComparer.OrdinalIgnoreCase.Compare(record.Name, processorName) == 0)) && !string.IsNullOrEmpty(record.Class))
                    {
                        string assemblyFile = string.Empty;
                        Assembly assembly = null;
                        try
                        {
                            if (!string.IsNullOrEmpty(record.Assembly))
                            {
                                assemblyFile = record.Assembly;
                                assembly = Assembly.Load(record.Assembly);
                            }
                            else if (!string.IsNullOrEmpty(record.CodeBase))
                            {
                                assemblyFile = record.CodeBase;
                                assembly = Assembly.LoadFrom(assemblyFile);
                            }
                            if (assembly == null)
                            {
                                throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.TypeResolveFailed, new object[] { processorName }));
                            }
                        }
                        catch (Exception exception)
                        {
                            this.LogError(true, string.Format(CultureInfo.CurrentCulture, Resources.DPAssemblyLoadFail, new object[] { assemblyFile, processorName, exception.ToString() }));
                            throw;
                        }
                        //if (!assembly.get_PermissionSet().IsUnrestricted())
                        //{
                        //    this.LogError(true, string.Format(CultureInfo.CurrentCulture, Resources.NoFullTrust, new object[] { assembly.FullName, processorName }));
                        //    throw new SecurityException(string.Format(CultureInfo.CurrentCulture, Resources.NoFullTrust, new object[] { assembly.FullName, processorName }));
                        //}
                        if (!assembly.PermissionSet.IsUnrestricted())
                        {
                            this.LogError(true, string.Format(CultureInfo.CurrentCulture, Resources.NoFullTrust, new object[] { assembly.FullName, processorName }));
                            throw new SecurityException(string.Format(CultureInfo.CurrentCulture, Resources.NoFullTrust, new object[] { assembly.FullName, processorName }));
                        }
                        Type type = null;
                        try
                        {
                            type = assembly.GetType(record.Class);
                            if (type == null)
                            {
                                throw new InvalidCastException(string.Format(CultureInfo.CurrentCulture, Resources.TypeResolveFailed, new object[] { processorName }));
                            }
                        }
                        catch (Exception exception2)
                        {
                            this.LogError(true, string.Format(CultureInfo.CurrentCulture, Resources.DPTypeLoadFail, new object[] { record.Class, assemblyFile, processorName, exception2.ToString() }));
                            throw;
                        }
                        return type;
                    }
                }
            }
            catch (Exception exception3)
            {
                throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.TypeResolveFailed, new object[] { processorName }), exception3);
            }
            throw new FileNotFoundException(string.Format(CultureInfo.CurrentCulture, Resources.TypeResolveFailed, new object[] { processorName }));
        }

        public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
        {
            bool flag = false;
            string str = string.Empty;
        //    using (StringEnumerator enumerator = this.Parameters.GetEnumerator())
        //    {
        //        while (enumerator.MoveNext())
        //        {
        //            string[] strArray = enumerator.Current.Split(new char[] { '!' }, 4);
        //            if (((string.IsNullOrEmpty(strArray[0]) || StringEqual(processorName, strArray[0])) && (string.IsNullOrEmpty(strArray[1]) || StringEqual(directiveId, strArray[1]))) && StringEqual(parameterName, strArray[2]))
        //            {
        //                flag = true;
        //                str = strArray[3];
        //                goto Label_0094;
        //            }
        //        }
        //    }
        //Label_0094:
        //    if (flag)
        //    {
        //        return str;
        //    }
            foreach (string  enumerator in this.Parameters)
            {
               
                    string[] strArray = enumerator.Split(new char[] { '!' }, 4);
                    if (((string.IsNullOrEmpty(strArray[0]) || StringEqual(processorName, strArray[0])) && (string.IsNullOrEmpty(strArray[1]) || StringEqual(directiveId, strArray[1]))) && StringEqual(parameterName, strArray[2]))
                    {
                        flag = true;
                        str = strArray[3];
                        break;
                    }                
            }
       
            if (flag)
            {
                return str;
            }
            return string.Empty;
        }

        public string ResolvePath(string path)
        {
            if (Path.IsPathRooted(path))
            {
                return path;
            }
            string str2 = Path.Combine(Path.GetDirectoryName(this.inputFile), path);
            if (!File.Exists(str2) && !Directory.Exists(str2))
            {
                return path;
            }
            return str2;
        }

        protected override void RunImplementation()
        {
            base.ReturnCode = 0;
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(this.AssemblyResolver);
            this.engine = new Engine();
            string content = "";
            if (this.outputEncoding == null)
            {
                this.outputEncoding = EncodingHelper.GetEncoding(this.inputFile);
            }
            if (this.outputEncoding == null)
            {
                this.outputEncoding = Encoding.UTF8;
            }
            string path = "";
            content = File.ReadAllText(this.inputFile);
            string s = this.engine.ProcessTemplate(content, this);
            if (!string.IsNullOrEmpty(this.OutputFile))
            {
                path = this.OutputFile;
            }
            else
            {
                path = Path.Combine(Path.GetDirectoryName(this.inputFile), Path.GetFileNameWithoutExtension(this.inputFile)) + this.extension;
            }
            byte[] bytes = this.outputEncoding.GetBytes(s);
            byte[] preamble = this.outputEncoding.GetPreamble();
            if ((preamble != null) && (preamble.Length > 0))
            {
                bool flag = false;
                if (bytes.Length >= preamble.Length)
                {
                    flag = true;
                    for (int i = 0; i < preamble.Length; i++)
                    {
                        if (preamble[i] != bytes[i])
                        {
                            flag = false;
                            break;
                        }
                    }
                }
                if (!flag)
                {
                    byte[] array = new byte[preamble.Length + bytes.Length];
                    preamble.CopyTo(array, 0);
                    bytes.CopyTo(array, preamble.Length);
                    bytes = array;
                }
            }
            using (FileStream stream = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                stream.Write(bytes, 0, bytes.Length);
            }
            this.engine = null;
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(this.AssemblyResolver);
        }

        public void SetFileExtension(string extension)
        {
            this.extension = extension;
        }

        public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
        {
            if (!this.encodingSetFromOutputDirective)
            {
                if (fromOutputDirective)
                {
                    this.encodingSetFromOutputDirective = true;
                    this.outputEncoding = encoding;
                }
                else
                {
                    if ((this.outputEncoding != null) && (encoding != this.outputEncoding))
                    {
                        this.outputEncoding = Encoding.UTF8;
                    }
                    this.outputEncoding = encoding;
                }
            }
        }

        private static bool StringEqual(string first, string second)
        {
            return (StringComparer.OrdinalIgnoreCase.Compare(first, second) == 0);
        }

        [Option("dp", OptionValueType.ValueOptional, Description="-{0} <directiveprocessorname!directiveprocessortype![assembly|codebase]>: Specify the name, full type name, and assembly of a directive processor that can be used to process custom directives within the text template", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection DirectiveProcessors
        {
            get
            {
                return this.directiveProcessors;
            }
        }

        [Option("u", OptionValueType.ValueOptional, Description="-{0} <namespace>: Import a namespace for use within the text template", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection Imports
        {
            get
            {
                return this.imports;
            }
        }

        [Option("I", OptionValueType.ValueOptional, Description="-{0} <includedirectory>: Provide a directory containing text templates included from within the given text template", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection IncludeDirectories
        {
            get
            {
                return this.includeDirectories;
            }
        }

        [Option("out", OptionValueType.ValueOptional, Description="-{0} <filename>: The file to write the output of the transform to.")]
        internal string OutputFile
        {
            get
            {
                return this.outputFile;
            }
            set
            {
                this.outputFile = value;
            }
        }

        [Option("a", OptionValueType.ValueOptional, Description="-{0} <[directiveprocessor]![directiveid]!parametername!parametervalue>: Specify a parameter that a directive processor can query for as a name/value pair.\r\nThe directive processor and identifier are optional allowing parameters to be specified for any directive processor or any instance of a particular directive processor.", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection Parameters
        {
            get
            {
                return this.parameters;
            }
        }

        [Option("P", OptionValueType.ValueOptional, Description="-{0} <referencepath>: Provide a directory to search for referenced assemblies specified within the text template or using the -r option", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection ReferencePaths
        {
            get
            {
                return this.referencePaths;
            }
        }

        [Option("r", OptionValueType.ValueOptional, Description="-{0} <assembly>: Reference an assembly for compiling and running the text template", IsMultipleValue=true, CollectionType=typeof(string))]
        internal StringCollection References
        {
            get
            {
                return this.references;
            }
        }

        public IList<string> StandardAssemblyReferences
        {
            get
            {
                List<string> list = new List<string> { "System", "WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" };
                foreach (string str in this.References)
                {
                    list.Add(str);
                }
                return list.ToArray();
            }
        }

        public IList<string> StandardImports
        {
            get
            {
                List<string> list = new List<string> { "System" };
                foreach (string str in this.Imports)
                {
                    list.Add(str);
                }
                return list.ToArray();
            }
        }

        public string TemplateFile
        {
            get
            {
                return this.inputFile;
            }
        }
    }
}

